// UnicodeMethod.cpp: implementation of the CUnicodeMethod class.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "UnicodeMethod.h"

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

const int   SIZE_WCHAR = sizeof(wchar_t);

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CUnicodeMethod::CUnicodeMethod()
{
}

CUnicodeMethod::~CUnicodeMethod()
{
}

// Return Value < 0 : Error
// Return Value >= 0 : Success

// ifpszOutput == NULL || nOutputButes == 0, --> Return Value is Needing Output Buffer Size.
// nInputBytes < 0 --> pszInput Data is Null Terminated String.. automatically calculate string length..
int CUnicodeMethod::_UnicodeToAnsi(const STREAM* pszInput, const int& nInputBytes, 
                                   STREAM* pszOutput, const int& nOutputBytes, 
                                   const int& nInputCodeType)
{
    int     nRet = 0;

    if(pszInput == NULL)
    {
        return -1;
    }
    else if(nInputBytes == 0)
    {
        return 0;
    }
    
    if(nInputCodeType == TYPE_UTF16)
    {
        if(nInputBytes % 2 && nInputBytes >=0)
        {
            // szInput is not UTF-16 buffer..
            return -1;
        }

        wchar_t* pwcInput = (wchar_t*)pszInput;
        int      nLength = 0;

        // Null Terminated String..
        if(nInputBytes == -1)
        {
            nLength = wcslen(pwcInput);
        }
        else
        {
            nLength = nInputBytes / SIZE_WCHAR;
        }

        int     nAnsiBytes(nOutputBytes);

        // Current Mode is Querying Ansi Bytes Size..
        if(pszOutput == NULL || nOutputBytes == 0)
        {
            nAnsiBytes = WideCharToMultiByte(CP_ACP, 0, pwcInput, nLength, 
                                             NULL, 0, NULL, NULL);
            
            if(nAnsiBytes == 0)
            {
                return -1;
            }

            return nAnsiBytes;
        }
        
        // Insufficent Buffer Size..
        if(nAnsiBytes > nOutputBytes || nOutputBytes < 0)
        {
            return -1;
        }

        memset(pszOutput, 0, nOutputBytes);
                
        return WideCharToMultiByte(CP_ACP, 0, pwcInput, nLength,
                                   pszOutput, nOutputBytes, NULL, NULL);
    }
    else if(nInputCodeType == TYPE_UTF8)
    {
        // UTF8 -> UTF16
        int     nUTF16Bytes = _UTF8ToUTF16(pszInput, nInputBytes, NULL, NULL);

        if(nUTF16Bytes <= 0)
        {
            return nUTF16Bytes;
        }
                
        STREAM* pszUTF16 = new STREAM[nUTF16Bytes];
        nUTF16Bytes = _UTF8ToUTF16(pszInput, nInputBytes, pszUTF16, nUTF16Bytes);

        // Current Mode is Querying Ansi Bytes Size..
        if(pszOutput == NULL || nOutputBytes == 0)
        {
            nRet = _UnicodeToAnsi(pszUTF16, nUTF16Bytes, NULL, NULL, TYPE_UTF16);
        }
        else
        {
            // UTF16 -> ANSI
            nRet = _UnicodeToAnsi(pszUTF16, nUTF16Bytes, pszOutput, 
                                  nOutputBytes, TYPE_UTF16);	
        }
        delete[] pszUTF16;
        return nRet;
    }
    
    // Invalid Code Type..
    return 0;
}

int CUnicodeMethod::UnicodeToAnsi(const STREAM* pszInput, const int& nInputBytes, 
                                  STREAM* pszOutput, const int& nOutputBytes, 
                                  const int& nInputCodeType, 
                                  const BOOL& bIsNullTerminated)
{
    int     nDataBytes = 0;

    if(bIsNullTerminated)
    {
        nDataBytes = _UnicodeToAnsi(pszInput, nInputBytes, pszOutput, 
                                    nOutputBytes, nInputCodeType);		
        if(nDataBytes >= 0)
        {
            nDataBytes += sizeof(char);
        }
    }
    else
    {
        nDataBytes = _UnicodeToAnsi(pszInput, nInputBytes, pszOutput, 
                                    nOutputBytes, nInputCodeType);
    }

    return nDataBytes;
}
    
int CUnicodeMethod::_AnsiToUnicode(const STREAM* pszInput, int nInputBytes, 
                                   STREAM* pszOutput, const int& nOutputBytes, 
                                   const int& nOutputCodeType)
{
    int     nRet = 0;

    if(pszInput == NULL)
    {
        return -1;
    }
    else if(nInputBytes == 0)
    {
        return 0;
    }

    // pszInput is NULL terminated String.. automatically calculate length..
    if(nInputBytes < 0)
    {
        nInputBytes = strlen(pszInput);
    }

    // ANSI -> UTF16
    int     nUTF16Size = (nOutputCodeType == TYPE_UTF16) ? (nOutputBytes / SIZE_WCHAR) : nOutputBytes;

    if(pszOutput == NULL || nOutputBytes == 0)
    {
        nUTF16Size = MultiByteToWideChar(CP_ACP, MB_COMPOSITE, pszInput, 
                                         nInputBytes, 0, 0);
        
        // Error..
        if(nUTF16Size == 0)
        {
            return -1;
        }

        // Current Mode is Querying Unicode Bytes Size..
        if(nOutputCodeType == TYPE_UTF16)
        {
            return nUTF16Size * SIZE_WCHAR;
        }

        return nUTF16Size;
    }

    wchar_t*    pwcUTF16 = new wchar_t[nUTF16Size];

    nUTF16Size = MultiByteToWideChar(CP_ACP, MB_COMPOSITE, pszInput, nInputBytes, 
                                     pwcUTF16, nUTF16Size);
    if(nUTF16Size == 0)
    {
        delete[] pwcUTF16;
        return -1;
    }

    if(nOutputCodeType == TYPE_UTF16)
    {
        // Insufficent output buffer size..
        if(nOutputBytes < (int)(nUTF16Size * SIZE_WCHAR) || nOutputBytes < 0)
        {
            delete[] pwcUTF16;
            return -1;
        }
        
        memset(pszOutput, 0, nOutputBytes);
        memcpy(pszOutput, pwcUTF16, nUTF16Size * SIZE_WCHAR);
        
        delete[] pwcUTF16;
        return nUTF16Size * SIZE_WCHAR;
    }
    else if(nOutputCodeType == TYPE_UTF8)
    {
        int     nUTF8Bytes = _UTF16ToUTF8((const STREAM*)pwcUTF16, nUTF16Size * SIZE_WCHAR, 
                                          NULL, NULL);

        // Current Mode is Querying Unicode Bytes Size..
        if(pszOutput == NULL || nOutputBytes == 0)
        {
            delete[] pwcUTF16;
            return nUTF8Bytes;
        }

        // Insufficent output buffer size..
        if(nOutputBytes < nUTF8Bytes || nOutputBytes < 0)
        {
            delete[] pwcUTF16;
            return -1;
        }
        
        memset(pszOutput, 0, nOutputBytes);
        nUTF8Bytes = _UTF16ToUTF8((const STREAM*)pwcUTF16, nUTF16Size * SIZE_WCHAR, 
                                  pszOutput, nOutputBytes);
        delete[] pwcUTF16;
        return nUTF8Bytes;
    }

    // Invalid Output code Type.
    return -1;
}

int CUnicodeMethod::AnsiToUnicode(const STREAM* pszInput, const int& nInputBytes, 
                                  STREAM* pszOutput, const int& nOutputBytes, 
                                  const int& nOutputCodeType, 
                                  const BOOL& bIsNullTerminated)
{
    int     nDataBytes = 0;

    if(bIsNullTerminated)
    {
        nDataBytes = _AnsiToUnicode(pszInput, nInputBytes, pszOutput, 
                                    nOutputBytes, nOutputCodeType);

        if(nDataBytes >= 0)
        {
            if(nOutputCodeType == TYPE_UTF16)
            {
                nDataBytes += SIZE_WCHAR;
            }
            else if(nOutputCodeType == TYPE_UTF8)
            {
                nDataBytes += sizeof(char);
            }
        }
    }
    else
    {
        nDataBytes = _AnsiToUnicode(pszInput, nInputBytes, pszOutput, 
                                    nOutputBytes, nOutputCodeType);
    }

    return nDataBytes;
}

int CUnicodeMethod::_UTF8ToUTF16(const STREAM* pszInput, int nInputBytes,
                                 STREAM* pszOutput, const int& nOutputBytes)
{
    if(pszInput == NULL)
    {
        return -1;
    }
    else if(nInputBytes == 0)
    {
        return 0;
    }
    
    // pszInput is NULL terminated String.. automatically calculate length..
    if(nInputBytes < 0)
    {
        nInputBytes = strlen(pszInput);
    }
    
    int     nResult(nOutputBytes / SIZE_WCHAR);

    // Calculate buffer size mode..
    if(pszOutput == NULL || nOutputBytes == 0)
    {
        nResult = MultiByteToWideChar(CP_UTF8, 0, pszInput, nInputBytes, 0, 0);

        if(nResult == 0)
        {
            return -1;
        }

        return nResult * SIZE_WCHAR;
    }

    // Insufficent output buffer
    if(nOutputBytes < (int)(nResult * SIZE_WCHAR) || nOutputBytes < 0)
    {
        return -1;
    }
    
    memset(pszOutput, 0, nOutputBytes);
    wchar_t*    pTemp = (wchar_t*)(pszOutput);
    
    nResult = MultiByteToWideChar(CP_UTF8, 0, pszInput, nInputBytes, pTemp, 
                                  nResult * SIZE_WCHAR);

    if(nResult == 0)
    {
        return -1;
    }

    return nResult * SIZE_WCHAR;
}

int CUnicodeMethod::UTF8ToUTF16(const STREAM* pszInput, const int& nInputBytes,
                                STREAM* pszOutput, const int& nOutputBytes, 
                                const BOOL& bIsNullTerminated)
{
    int     nDataBytes = 0;

    if(bIsNullTerminated)
    {
        nDataBytes = _UTF8ToUTF16(pszInput, nInputBytes, pszOutput, nOutputBytes);
        if(nDataBytes >= 0)
        {
            nDataBytes += SIZE_WCHAR;
        }
    }
    else
    {
        nDataBytes = _UTF8ToUTF16(pszInput, nInputBytes, pszOutput, nOutputBytes);
    }

    return nDataBytes;
}

int CUnicodeMethod::_UTF16ToUTF8(const STREAM* pszInput, const int& nInputBytes,
                                 STREAM* pszOutput, const int& nOutputBytes)
{
    if(pszInput == NULL)
    {
        return -1;
    }
    else if(nInputBytes == 0)
    {
        return 0;
    }
    
    wchar_t*    pwcInput = (wchar_t*)(pszInput);
    int         nInputSize = 0;

    if(nInputBytes < 0)
    {
        nInputSize = wcslen(pwcInput);
    }
    else
    {
        // pszInput must be wchar buffer..
        if(nInputBytes % SIZE_WCHAR)
        {
            return -1;
        }
        nInputSize = nInputBytes / SIZE_WCHAR;
    }

    int     nUTF8Size(nOutputBytes);

    // Calculate buffer size mode..
    if(pszOutput == NULL || nOutputBytes == 0)
    {
        nUTF8Size = WideCharToMultiByte(CP_UTF8, 0, pwcInput, nInputSize, 
                                        0, 0, NULL, NULL);

        if(nUTF8Size == 0)
        {
            return -1;
        }

        return nUTF8Size;	
    }

    // Insufficent output buffer size..
    if(nOutputBytes < nUTF8Size || nOutputBytes < 0)
    {
        return -1;
    }

    memset(pszOutput, 0, nOutputBytes);
    nUTF8Size = WideCharToMultiByte(CP_UTF8, 0, pwcInput, nInputSize, 
                                    pszOutput, nOutputBytes, NULL, NULL);
    
    return nUTF8Size;
}

int CUnicodeMethod::UTF16ToUTF8(const STREAM* pszInput, const int& nInputBytes,
                                STREAM* pszOutput, const int& nOutputBytes, 
                                const BOOL& bIsNullTerminated)
{	
    int nDataBytes = 0;

    if(bIsNullTerminated)
    {
        nDataBytes = _UTF16ToUTF8(pszInput, nInputBytes, pszOutput, nOutputBytes);
        if(nDataBytes >= 0)
        {
            nDataBytes += sizeof(char);
        }
    }
    else
    {
        nDataBytes = _UTF16ToUTF8(pszInput, nInputBytes, pszOutput, nOutputBytes);
    }

    return nDataBytes;
}

int CUnicodeMethod::CheckCodeType(const STREAM* szInput, const int& nInputBytes, 
                                  int& nBytesBOM)
{
    if(!szInput)
    {
        return -1;
    }

    nBytesBOM = 0;

    if(nInputBytes == 0)
    {
        return TYPE_ANSI;
    }
    
    if(nInputBytes >= 2)
    {
        if(szInput[0] == char(0xFF) && szInput[1] == char(0xFE))
        {
            nBytesBOM = 2;
            return TYPE_UTF16;
        }
        else if(szInput[0] == char(0xFF) && szInput[1] == char(0xEF))
        {
            nBytesBOM = 2;
            return TYPE_UTF16;
        }
    }

    if(nInputBytes >= 2)
    {
        if(szInput[0] == char(0xFE) && szInput[1] == char(0xFF))
        {
            nBytesBOM = 2;
            return TYPE_UTF16_B;
        }
        else if(szInput[0] == char(0xEF) && szInput[1] == char(0xFF))
        {
            nBytesBOM = 2;
            return TYPE_UTF16_B;
        }
    }

    if(nInputBytes >= 3)
    {
        if(szInput[0] == char(0xE0)) 
        {
            if(szInput[1] >= char(0xA0) && szInput[1] <= char(0xBF)) 
            {
                if(szInput[2] >= char(0x80) && szInput[2] <= char(0xBF))
                {
                    nBytesBOM = 3;
                    return TYPE_UTF8;
                }
            }
        }

        if(szInput[0] >= 0xE1 && szInput[0] <= 0xEC)
        {
            if(szInput[1] >= 0x80 && szInput[1] <= 0xBF)  
            {
                if(szInput[2] >= 0x80 && szInput[2] <= 0xBF)
                {
                    nBytesBOM = 3;
                    return TYPE_UTF8;
                }
            }
        }

        if(szInput[0] >= 0xE1 && szInput[0] <= 0xEC)
        {
            if(szInput[1] >= 0x80 && szInput[1] <= 0xBF)
            {
                if(szInput[2] >= 0x80 && szInput[2] <= 0xBF)
                {
                    nBytesBOM = 3;
                    return TYPE_UTF8;
                }
            }
        }

        if(szInput[0] == 0xED)
        {
            if(szInput[1] >= 0x80 && szInput[1] <= 0x9F)
            {
                if(szInput[2] >= 0x80 && szInput[2] <= 0xBF)
                {
                    nBytesBOM = 3;
                    return TYPE_UTF8;
                }
            }
        }
                                    
        if(szInput[0] >= 0xEE && szInput[0] <= 0xEF)
        {
            if(szInput[1] >= 0x80 && szInput[1] <= 0xBF)
            {
                if(szInput[2] >= 0x80 && szInput[2] <= 0xBF)
                {
                    nBytesBOM = 3;
                    return TYPE_UTF8;
                }
            }
        }
    }
    
    return TYPE_ANSI;
}

// Support Only UTF16. So nBytes must be Even.
BOOL CUnicodeMethod::ToggleEndianType(STREAM* szBuffer, const int& nBytes)
{
    if(!szBuffer || nBytes <= 0 || nBytes % 2 != 0)
    {
        return FALSE;
    }
    
    unsigned short* pTemp = (unsigned short*)(szBuffer);

    for(unsigned int i=0; i < nBytes / sizeof(unsigned short); i++)
    {
        pTemp[i] = (unsigned short)(((pTemp[i] >> 8) & 0x00FF) | 
                                    ((pTemp[i] << 8) & 0xFF00));		
    }
    
    return TRUE;
}
